using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Entropy.Ext;
using Entropy.Ext.Ast;

public class SemanticParser
{
	Dictionary<string, bool> paramsSeen;
    private bool treatAsJson;
	private IAccent accent;
	private Dictionary<AstConstructor, Node> ctors;
	private Dictionary<Node, string> pronouncedNames;
    private readonly Variable ext = new Variable {Name = "Ext"};

    protected Variable Ext
    {
        get { return ext; }
    }

    public static Node[] ParseExpressions(Expression[] exprs, IAccent accent, Dictionary<Node, string> pronouncedNames)
	{

		var result = new List<Node>();
		var ctors = new Dictionary<AstConstructor, Node>();

		foreach (var expr in exprs)
		{
			var parser = new SemanticParser { accent = accent, ctors = ctors, pronouncedNames = pronouncedNames};
			result.Add(parser.ParseNode(expr));
		}

		// exclude root ctors that used as json
		result = result.Where(n => ctors.ContainsValue(n) || ! (n is VariableDeclareOrRefer)).ToList();

		return result.ToArray();
	}

	private Node ParseConstructor(AstConstructor expr)
	{
		Node result;
		if (ctors.TryGetValue(expr, out result))
		{
            if (treatAsJson)
				ctors.Remove(expr);
			else
				return result;
		}

		var json = new Json
		{
			ClassInfo = ClassResolver.ResolveClassName(expr.Type),
			Props = expr.Args.Select(arg =>
				new SubArgument
				{
					Name = arg.Name,
					Value = ParseNodeBoxed(arg.Value, arg.AsJson)
				}).ToArray()
		};

		result = accent.Constructor(json, treatAsJson, expr.InsideEventHandler);
        if (!expr.InsideEventHandler && !treatAsJson)
		{
			ctors.Add(expr, result);
			if (expr.PronouncedName != null)
				pronouncedNames.Add(result, expr.PronouncedName);
		}
		return result;
	}

	private Node ParseNodeBoxed(Expression value, bool treatAsJsonValue)
	{
		var oldValue = treatAsJson;
		treatAsJson = treatAsJsonValue;
		var result = ParseNode(value);
		treatAsJson = oldValue;
		return result;
	}

	private Node ParseNode(Expression body)
	{
		if (body is AstExpression)
			return ParseCustomNode(body); 

		switch (body.NodeType)
		{
			case ExpressionType.Constant:
				return ParseConstant((ConstantExpression) body);
			case ExpressionType.Conditional:
				return ParseConditional((ConditionalExpression) body);
			
			case ExpressionType.NotEqual:
				return ParseBinary((BinaryExpression) body, BinaryOperator.NotEqual);

			case ExpressionType.Equal:
				return ParseBinary((BinaryExpression) body, BinaryOperator.Equal);

			case ExpressionType.GreaterThan:
				return ParseBinary((BinaryExpression) body, BinaryOperator.GreaterThan);

			case ExpressionType.GreaterThanOrEqual:
				return ParseBinary((BinaryExpression) body, BinaryOperator.GreaterThanOrEqual);

			case ExpressionType.LessThan:
				return ParseBinary((BinaryExpression) body, BinaryOperator.LessThan);

			case ExpressionType.LessThanOrEqual:
				return ParseBinary((BinaryExpression) body, BinaryOperator.LessThanOrEqual);

			case ExpressionType.Parameter:
				return ParseParameter((ParameterExpression)body);

			case ExpressionType.Add:
				return ParseBinary((BinaryExpression)body, BinaryOperator.Add);

			case ExpressionType.Not:
				return ParseUnary((UnaryExpression)body, UnaryOperator.Not);

			case ExpressionType.AndAlso:
                return ParseBinary((BinaryExpression)body, BinaryOperator.AndAlso);

			case ExpressionType.OrElse:
                return ParseBinary((BinaryExpression)body, BinaryOperator.OrElse);

			case ExpressionType.Assign:
                return ParseBinary((BinaryExpression)body, BinaryOperator.Assign);

			case ExpressionType.MemberAccess:
                return ParseMemberAccess((MemberExpression)body);
			
			case ExpressionType.Call:
                return ParseCall((MethodCallExpression)body);

			case ExpressionType.Convert:
                return ParseUnary((UnaryExpression)body, UnaryOperator.Convert);

			case ExpressionType.NewArrayInit:
                return ParseNewArray((NewArrayExpression)body);

			case ExpressionType.Quote:
                return ParseUnary((UnaryExpression) body, UnaryOperator.Quote);

			case ExpressionType.Lambda:
                return ParseLambda((LambdaExpression) body);

			case ExpressionType.ArrayIndex:
                return ParseBinary((BinaryExpression)body, BinaryOperator.ArrayIndex);

			case ExpressionType.New:
                return ParseNew((NewExpression)body);

			case ExpressionType.MemberInit:
                return ParseMemberInit((MemberInitExpression)body);

			case ExpressionType.Invoke:
                return ParseInvocation((InvocationExpression)body);

			case ExpressionType.Block:
                return ParseBlock((BlockExpression)body);

			case ExpressionType.Goto:
                return ParseGoto((GotoExpression)body);

			case ExpressionType.Label:
                return ParseLabel((LabelExpression)body);

			case ExpressionType.Index:
                return ParseIndex((IndexExpression)body);

			case ExpressionType.Default:
                return ParseDefault((DefaultExpression)body);

			default:
				throw new Exception("Unexpected node: " + body.NodeType);
		}
	}

	private Node ParseDefault(DefaultExpression body)
	{
		return new Default();
	}

	private Node ParseIndex(IndexExpression body)
	{
		return new Binary { Left = ParseNode(body.Object), Right = ParseNode(body.Arguments.Single()), Operator = BinaryOperator.ArrayIndex };
	}

	private Node ParseLabel(LabelExpression body)
	{
		return new Label();
	}

	private Node ParseGoto(GotoExpression body)
	{
		switch (body.Kind)
		{
			case GotoExpressionKind.Return:
				return new Return {Value = ParseNode(body.Value)};
		}
		throw new Exception("Unexpected goto node: " + body.Kind);
	}

	private Node ParseBlock(BlockExpression body)
	{
		return new Block
		{
			Children = body.Expressions.Select(ParseNode).ToArray()
		};
	}

	private Node ParseInvocation(InvocationExpression body)
	{
		
		return ParseNode(body.Expression);
	}

	private Node ParseMemberInit(MemberInitExpression body)
	{
		var node = (Json)ParseNew(body.NewExpression);
		var props = new List<SubArgument>(node.Props);
		foreach (var binding in body.Bindings)
		{
			var memberAssignment = binding as MemberAssignment;
			if (memberAssignment != null)
			{
				props.Add(new SubArgument
				{
					Name = memberAssignment.Member.Name,
					Value = ParseNode(memberAssignment.Expression)
				});
			}
		}
		node.Props = props.ToArray();
		return node;
	}

	private Node ParseNew(NewExpression body)
	{
	    var subArguments = body.Members.Zip(body.Arguments, (member, expr) => 
            new SubArgument
	        {
	            Name = RestoreReservedWords(member.Name), Value = ParseNode(expr)
	        });
        return new Json { Props = subArguments.ToArray() };
	}

    private static string RestoreReservedWords(string name)
	{
		switch (name)
		{
			case "paramz":
				return "params";
		}
		return name;
	}

	private Node ParseLambda(LambdaExpression lambda)
	{

		var paramz = new List<string>();
		var expr = lambda.Body;

		var saveParams = paramsSeen;
		paramsSeen = new Dictionary<string, bool>();
		
		var node = ParseNode(expr);
		if (node is FunctionDefinition)
			return node;

		int lastSeenIndex = 0;
		for (int i = 0; i < lambda.Parameters.Count; i++)
		{
			var paramExpr = lambda.Parameters[i];
			bool seen;
			paramsSeen.TryGetValue(paramExpr.Name, out seen);
			if (seen)
				lastSeenIndex = i;
		}

		for (int i = 0; i <= lastSeenIndex; i++)
		{
			paramz.Add(lambda.Parameters[i].Name);
		}

		paramsSeen = saveParams;

		return new FunctionDefinition { Root = node, Parameters = paramz.ToArray() };
	}

	private Node ParseNewArray(NewArrayExpression node)
	{
		return new ArrayInit { 
			Elems = node.Expressions.Select(ParseNode).ToArray()
		};
	}

	private Node ParseCustomNode(Expression expr)
	{
		if (expr is AstConstructor)
			return ParseConstructor((AstConstructor) expr);
        if (expr is RootFactoryAccess)
            return new Reference {Variable = Ext};
		throw new Exception("Unexpected: " + expr.GetType().Name);
	}

    private Call ParseCall(MethodCallExpression node)
    {
        return new Call {
            Object = ParseNode(node.Object), 
            FunctionName = node.Method.Name, 
            Arguments = node.Arguments.Select(ParseNode).ToArray()};
    }

	private Node ParseMemberAccess(MemberExpression node)
    {
        MemberInfo memberInfo = node.Member;
        if (node.Expression.NodeType == ExpressionType.Constant)
		{
		    var ce = (ConstantExpression) node.Expression;
		    var reference = ((FieldInfo) memberInfo).GetValue(ce.Value);
		    var proxy = ((IInterfacedFactoryAccess) reference).Access();
            var intfFactory = (InterfacedFactory)proxy;
		    var expression = intfFactory.Me;
		    expression.PronouncedName = memberInfo.Name;
		    return ParseNode(expression);
		}
	    Node obj = ParseNode(node.Expression);
	    return new PropertyGet { Object = obj, PropName = memberInfo.Name };
    }

    private Node ParseBinary(BinaryExpression node, BinaryOperator op)
	{
		return new Binary { Left = ParseNode(node.Left), Right = ParseNode(node.Right), Operator = op };
	}

    private Node ParseUnary(UnaryExpression node, UnaryOperator op)
    {
	    switch (op)
	    {
			case UnaryOperator.Quote:
			case UnaryOperator.Convert:
			    return ParseNode(node.Operand);
	    }
        return new Unary { Operand = ParseNode(node.Operand), Operator = op};
    }

	private Node ParseConditional(ConditionalExpression body)
	{
		return new Conditional
		{
			Test = ParseNode(body.Test), 
			IfTrue = ParseNode(body.IfTrue), 
			IfFalse = ParseNode(body.IfFalse)
		};
	}

	private static Node ParseConstant(ConstantExpression body)
	{
		return new Const(body.Value);
	}
	private Node ParseParameter(ParameterExpression node)
	{
		paramsSeen[node.Name] = true;
		return new Param {Name = node.Name};
	}
}




